package org.sergiy.forecast;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.ejb.Stateless;

import au.gov.bom.weather.AreaType;
import au.gov.bom.weather.BomWeatherService;
import au.gov.bom.weather.BomWeatherServiceException;
import au.gov.bom.weather.DefaultBomWeatherService;
import au.gov.bom.weather.ElementType;
import au.gov.bom.weather.ForecastPeriodType;
import au.gov.bom.weather.ForecastType;
import au.gov.bom.weather.ProductType;
import au.gov.bom.weather.TextElementType;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;

/**
 * Default implementation of weather service. 
 * It is our facade that hides dependency on BOM service. 
 * So it might be replaced with some other service 
 * 
 * @author Sergiy Litsenko
 *
 */
@Stateless
@Name("forecastManager")
@AutoCreate
public class DefaultWeatherService implements WeatherService {
	// logger
	protected Log log = LogFactory.getLog (this.getClass());

	// BOM service
	private BomWeatherService service;
	
	/**
	 * Get BOM code for the location
	 * @param location the location 
	 * @return BOM code for location
	 * @throws WeatherServiceException
	 */
	protected String getBomCode (String location) throws WeatherServiceException {
		// debug
		if (log.isDebugEnabled()) {
			log.debug(String.format("Trying to find BOM code for the location: '%1$s'", location));
		}
		
		// check Sydney region
		for (String knownLocation: BomWeatherService.SYDNEY_REGION) {
			if (knownLocation.equalsIgnoreCase(location)) {
				// debug
				if (log.isDebugEnabled()) {
					log.debug(String.format("Found BOM code for the location: '%1$s' - %2$s", 
							location, BomWeatherService.CODE_SYDNEY_REGION_FORECAST));
				}
				// return
				return BomWeatherService.CODE_SYDNEY_REGION_FORECAST;
			}
		}
		
		// check ACT region
		for (String knownLocation: BomWeatherService.ACT_REGION) {
			if (knownLocation.equalsIgnoreCase(location)) {
				// debug
				if (log.isDebugEnabled()) {
					log.debug(String.format("Found BOM code for the location: '%1$s' - %2$s", 
							location, BomWeatherService.CODE_CANBERRA_REGION_FORECAST));
				}
				// return
				return BomWeatherService.CODE_CANBERRA_REGION_FORECAST;
			}
		}
		
		// not found
		String errorMessage = String.format("Unable to find BOM code for the location: '%1$s'", location);
		log.error(errorMessage);
		throw new LocationNotFoundException (errorMessage);
	}
	
	/**
	 * Convert a list to string 
	 * @param list the list to convert
	 * @param delimiter the delimiter to use
	 * @return string representation of list
	 */
	protected String extractContents (List<Serializable> list, String delimiter) {
		StringBuffer sb = new StringBuffer();
		for (Serializable o : list) {
			sb.append(o);
			if (delimiter != null) {
				sb.append(delimiter);
			}
		}
		// return 
		return sb.toString();
	}
	
	/**
	 * Constructor 
	 */
	public DefaultWeatherService() {
		// set default service
		setService (new DefaultBomWeatherService());
	}

	/* (non-Javadoc)
	 * @see org.sergiy.forecast.WeatherService#getForecast(java.lang.String)
	 */
	@Override
	public Forecast getForecast(String location) throws WeatherServiceException {
		// debug
		if (log.isDebugEnabled()) {
			log.debug(String.format("Trying to get today's forecast for the location: '%1$s'", location));
		}
		
		// BOM service always returns XML document containing weekly forecast
		List<Forecast> weeklyForecast = getWeeklyForecast(location);
		// get the first one
		Forecast forecast = weeklyForecast != null && !weeklyForecast.isEmpty()? weeklyForecast.get(0) : null;
		
		// debug
		if (log.isDebugEnabled()) {
			log.debug(String.format("Today's forecast for the location: '%1$s' - %2$s", location, forecast));
		}
		
		// return forecast for today
		return forecast;
	}

	/* (non-Javadoc)
	 * @see org.sergiy.forecast.WeatherService#getWeeklyForecast(java.lang.String)
	 */
	@Override
	public List<Forecast> getWeeklyForecast(String location) throws WeatherServiceException {
		// debug
		if (log.isDebugEnabled()) {
			log.debug(String.format("Trying to get weekly forecast for the location: '%1$s'", location));
		}
		
		// container for 10 elements initially
		List<Forecast> weeklyForecast = new ArrayList<Forecast>(10);
		
		// service
		BomWeatherService bomService = getService();
		if (bomService != null) {
			// Get BOM code for the location
			String bomCode = getBomCode (location);
			
			// get product and extract forecast
			try {
				// get product
				ProductType product = service.getForecast(bomCode);
				if (product != null) {
					// get forecast type containing multiple areas
					ForecastType forecastType = product.getForecast();
					if (forecastType != null) {
						Forecast forecast = null;
						//
						// get location area and create forecast
						//
						for (AreaType area : forecastType.getArea()) {
							// found location
							if (area.getType().equalsIgnoreCase("location") && area.getDescription().equalsIgnoreCase(location)) {
								// debug
								if (log.isDebugEnabled()) {
									log.debug(String.format("Found Area: %1$s", area.getDescription()));
								}	
								//
								// get all periods (days)
								//
								for (ForecastPeriodType forecastPeriod : area.getForecastPeriod()) {
									// create forecast
									forecast = new Forecast ();
									// set location
									forecast.setLocationName(location);
									
									// start date
									forecast.setStartDate(forecastPeriod.getStartTimeLocal().toGregorianCalendar().getTime());
									// end date
									forecast.setEndDate(forecastPeriod.getEndTimeLocal().toGregorianCalendar().getTime());
									// process all elements
									for (ElementType et : forecastPeriod.getElement()) {
										if (et.getType().equalsIgnoreCase("forecast_icon_code")) {
											forecast.setIconCode(new Integer(et.getValue()));
											forecast.setImage(service.getImage(forecast.getIconCode()).toString());
										} else if (et.getType().equalsIgnoreCase("air_temperature_minimum")) {
											forecast.setAirTemperatureMinimum(new Integer(et.getValue()));
										} else if (et.getType().equalsIgnoreCase("air_temperature_maximum")) {
											forecast.setAirTemperatureMaximum(new Integer(et.getValue()));
										} else if (et.getType().equalsIgnoreCase("precipitation_range")) {
											forecast.setPrecipitationRange(et.getValue());
										}
									}
									// process all text elements
									for (TextElementType tet: forecastPeriod.getText()) {
										if (tet.getType().equalsIgnoreCase("precis")){
											forecast.setForecastBriefDescription(extractContents(tet.getContent(), " "));
										} else if (tet.getType().equalsIgnoreCase("probability_of_precipitation")){
											if (tet.getContent() != null && !tet.getContent().isEmpty()) { 
												forecast.setPrecipitationProbability(
														new Integer(tet.getContent().get(0).toString().replace("%", "")));
											}
										}
									}
									
									// add
									weeklyForecast.add(forecast);
								} // all days
							}
						} // Area - location
						
						//
						// get metropolitan area 
						//
						for (AreaType area : forecastType.getArea()) {
							// found location
							if (area.getType().equalsIgnoreCase("metropolitan") && area.getDescription().equalsIgnoreCase(location)) {
								//
								// get all periods (days)
								//
								for (ForecastPeriodType forecastPeriod : area.getForecastPeriod()) {
									Forecast f = weeklyForecast.get(forecastPeriod.getIndex().intValue()); 
									// process all text elements
									for (TextElementType tet: forecastPeriod.getText()) {
										if (tet.getType().equalsIgnoreCase("forecast")) {
											f.setForecastDetailedDescription(extractContents(tet.getContent(), " "));
										} else if (tet.getType().equalsIgnoreCase("fire_danger")) {
											f.setFireAlert(extractContents(tet.getContent(), " "));
										} else if (tet.getType().equalsIgnoreCase("uv_alert")) {
											f.setUvAlert(extractContents (tet.getContent(), " "));
										}
									}
									
									// debug
									if (log.isDebugEnabled()) {
										log.debug(String.format("Processed forecast: %1$s", f));
									}
								} // all days
							}
						} // metropolitan
					} // forecast type	
				} // product 
			} catch (Exception e) {
				// error
				String errorMessage = String.format("Unable to get forecast for the location: '%1$s', BOM code: '%2$s'", 
													location, bomCode);
				log.error(errorMessage, e);
				// throw
				throw new WeatherServiceException (errorMessage, e);
			}
			
		}
		
		// debug
		if (log.isDebugEnabled()) {
			log.debug(String.format("Weekly forecast for the location: '%1$s' - %2$s", location, weeklyForecast));
		}
		
		// return
		return weeklyForecast;
	}

	/**
	 * @return the service
	 */
	public BomWeatherService getService() {
		return service;
	}

	/**
	 * @param service the service to set
	 */
	public void setService(BomWeatherService service) {
		this.service = service;
	}

}
